package estadistec.logica.gestores;

import java.util.Hashtable;
import java.util.Stack;

import estadistec.excepciones.ExcepcionLlaveExistente;
import estadistec.excepciones.ExcepcionLlaveInexistente;
import estadistec.excepciones.ExcepcionNombreSinDefinir;

import estadistec.logica.dependencias.IBuscarNodo;

/**
 * Maneja a objetos manejables. Los manejables que maneja deben tener, al 
 * igual que los nodos, su propia llave que los pueda distinguir. Es además 
 * el que genera las llaves para todos los que las necesiten, para que, de 
 * esta forma, todas sean únicas.
 */
public abstract class Gestor<M extends IManejable> implements IBuscarNodo {

	/**
	 * Contador de la llave actual. Cuando se está creando el archivo tiene 
	 * el valor mínimo, luego adquiere el valor que está almacenado en el 
	 * archivo.
	 */
	static private Long _llaveActual;


	/**
	 * Constructor de la clase. Se llama únicamente una vez, al inicio
	 * del programa.
	 */
	protected Gestor()
	{
		if (_llavesLibres == null) {
			_llavesLibres = new Stack<Long>();
			_llaveActual = Long.MIN_VALUE;
		}
	}

	/**
	 * Repositorio para los objetos a manejar. 
	 * Cada clase que hereda se encarga de inicializarlo.
	 */
	protected Hashtable<Long, M> _manejables;


	/**
	 * Pila con las llaves que ya no se usan. Estas llaves se reasignan si
	 * alguno requiere alguna nueva.
	 */
	static private Stack<Long> _llavesLibres;


	/**
	 * Agrega un elemento del tipo IManejable a los gestados por este objeto.
	 * 
	 * @param manejado
	 *            Objeto a ser contenido por este gestor.
	 */
	public void agregar(M manejado) throws ExcepcionLlaveExistente 
	{
		Long llave = manejado.getLlave();
		if (_manejables.contains(llave))
			throw new ExcepcionLlaveExistente("Gestor->agregar");

		_manejables.put(llave, manejado);
	}


	/** 
	 * Elimina un elemento a partir de su llave.
	 * 
	 * @param llave Llave del elemento que se desea eliminar.
	 */
	public void remover(Long llave) {
		if (!_manejables.containsKey(llave))
			throw new ExcepcionLlaveInexistente("Llave : " + llave);
		_manejables.remove(llave);
	}


	/**
	 * Libera la llave. Método para liberar una llave que ya no se necesita
	 * 
	 * @param llave
	 *            llave siendo desocupada
	 */
	static protected synchronized void liberarLlave(Long llave) 
	{
		_llavesLibres.push(llave);
	}


	/**
	 * Genera las llaves. Genera una nueva llave para elementos gestionables
	 * 
	 * @return nueva llave
	 */
	static protected synchronized Long getNuevaLlave() {
		if (_llavesLibres.isEmpty())
			return _llaveActual++;
		else
			return _llavesLibres.pop();
	}


	/** 
	 * Busca entre los manejables aquel que tenga el mismo nombre dado.
	 * Este método se debe usar sobre todo para encontrar un manejable
	 * a partir de su nombre.
	 * 
	 * @param nombre Nombre del manejable que se quiere buscar.
	 * 
	 * @return El manejable que concuerda con el nombre.
	 */
	public M getManejableXNombre (String nombre) {
		for ( M manejable : this._manejables.values() )
			try {
				if ( manejable.getNombre().equals(nombre) )
					return manejable;
			} catch (ExcepcionNombreSinDefinir e) {
				e.imprimirPilaRastreo();
			}
		return null;
	}


	/** 
	 * Devuelve si existe o no el manejable por el nombre dado.
	 * Este método se debería usar sobre todo si se quiere verificar
	 * que un nombre dado por el usuario no está repetido.
	 * 
	 * @param nombre Nombre que se quiere buscar.
	 * 
	 * @return Verdadero si el nombre existe y falso si no.
	 */
	public boolean existeManejableXNombre (String nombre) {
		for ( M manejable : this._manejables.values() ) 
			try {
				if ( manejable.getNombre().equals(nombre) )
					return true;
			} catch (ExcepcionNombreSinDefinir e) {
				e.imprimirPilaRastreo();
			}
		return false;
	}

	/** 
	 * Índice más pequeño que no ha sido utilizado en ningún nombre.
	 * Al momento de llamar al constructor del Gestor respectivo,
	 * debe iniciar esta variable en 1.
	 */
	protected int _indNombre;

	/** 
	 * Nombre general del Manejable. Por ejemplo "Escritorio",
	 * "Tabla Normal", etc. Debe definirse en cada clase que herede
	 * de Gestor.
	 */
	abstract protected String nombreManejable();

	/** 
	 * Devuelve un nombre que no se ha utilizado. Al momento de crear un 
	 * nuevo componente, el usuario no ha asignado un nombre aún, por lo que 
	 * es necesario asignar un nombre.
	 */
	protected String nuevoNombreManejable() {
		String nombre = nombreManejable() + '_' +  _indNombre;
		_indNombre++;
		while (existeManejableXNombre(nombre)) {
			nombre = nombreManejable() + _indNombre;
			_indNombre++;
		}
		return nombre;
	}

	/** 
	 * Verifica que el nombre tenga la sintaxis correcta.
	 *
	 * @param nombrePropuesto Nombre de la variable.
	 * 
	 * @return true si la sintaxis del nombre es correcta, y false en caso
	 * contrario.
	 */
	public boolean verificarSintaxisNombre(String nombrePropuesto) {
		// Primero nos fijamos si tiene la sintaxis correcta.
		return (nombrePropuesto.matches("[_a-zA-Z]\\w*"));
	}

}
